﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace LPBToolsLib.NetSocket
{
    public delegate void UDPRecDataCallBack(object sender, EndPoint SourPoint, byte[] RecData);

    public abstract class UDPBase
    {
        /// <summary>
        /// 以广播方式发送数据，
        /// 注意：实际测试中，255.255.255.255是受限的广播，它不被路由发送，也不会被送到相同物理网络段上的所有主机，仅本机可以收到消息
        /// 建议使用：192.168.3.255方式，在192.168.3.x网段内进行广播，网络广播会被路由，并会发送到专门网络上的每台主机
        /// </summary>
        /// <param name="DestBindPort"></param>
        /// <param name="SendDatas"></param>
        public static void SendMsg(int DestBindPort, byte[] SendDatas)
        {
            if ((SendDatas != null)
                && (DestBindPort > 0) && (DestBindPort < 0xFFFF))
            {
                Socket fUDPCli = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
                fUDPCli.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
                IPEndPoint fDestEndP = new IPEndPoint(IPAddress.Broadcast, DestBindPort);
                fUDPCli.SendTo(SendDatas, fDestEndP);
            }
        }

        /// <summary>  
        /// 向特定ip的主机的端口发送数据报，忽略应答
        /// </summary>  
        public static void SendMsg(EndPoint fDestEndP, byte[] SendDatas)
        {
            if ((fDestEndP != null) && (SendDatas != null))
            {
                Socket fUDPCli = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
                fUDPCli.SendTo(SendDatas, fDestEndP);
            }
        }
        /// <summary>
        /// 用于监听的Socket
        /// </summary>
        protected Socket tUDPSocket { get; set; } = null;
        public Recorder_Error ErrorRecorder { get; set; } = null;
        public Recorder_RTxBytes RTxBytes { get; set; } = null;
        public Recorder_RunLog RunLog { get; set; } = null;
        public virtual bool IsOnListen { get; protected set; } = false;
        /// <summary>
        /// 本地监听端口
        /// </summary>
        public EndPoint ListenEndPoint { get; protected set; } = null;

        public virtual void CloseAll() { }
    }

    /// <summary>
    /// UDP服务器，使用异步模式处理收到的数据，可保证多客户端的高速可靠通讯
    /// 接口事件：OnRecDatas
    /// 接口函数：SendRespon：向客户端发送应答，后续可改为线程处理方式；
    /// </summary>
    public class UDPServer: UDPBase
    {
        public event UDPRecDataCallBack OnRecDatas;
        /// <summary>
        /// 隧道是否在监听
        /// </summary>
        public override bool IsOnListen { get { return (ListenEndPoint != null) && (tUDPSocket != null) && tUDPSocket.IsBound; } }
        
        /// <summary>
        /// 最后一次收到的客户端
        /// </summary>
        private EndPoint tReceClientEP  = new IPEndPoint(IPAddress.Any, 0);
        /// <summary>
        /// 用于监听的Socket
        /// </summary>
        //private Socket tUDPSocket { get; set; } = null;
        /// <summary>
        /// 隧道接收数据缓冲区
        /// </summary>
        private byte[] tReceFromCliBuf { get; } = new byte[1024];

        /// <summary>
        /// 构建函数
        /// </summary>
        /// <param name="tListenEP">本地监听的隧道入口</param>
        public UDPServer(EndPoint tListenEP = null)
        {
            StartListen(tListenEP);
        }
        public UDPServer(int SerPort)
        {
            StartListen(new IPEndPoint(IPAddress.Any, SerPort));
        }
        public bool StartListen(EndPoint tListenEP = null)
        {
            if (tListenEP != null)
                ListenEndPoint = tListenEP;
            if (ListenEndPoint != null)
            {
                tUDPSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
                RunLog?.setRecord(new Record("Bind:" + ListenEndPoint.ToString()));
                tUDPSocket.Bind(ListenEndPoint);
                // 采用异步方式的接收数据
                tUDPSocket.BeginReceiveFrom(tReceFromCliBuf, 0, tReceFromCliBuf.Length,
                     SocketFlags.None, ref tReceClientEP, RecFromCliCallback, tUDPSocket);
            }
            return IsOnListen;
        }

        /// <summary>
        /// 异步接收的回调函数
        /// </summary>
        /// <param name="tResult"></param>
        private void RecFromCliCallback(IAsyncResult tResult)
        {
            EndPoint tReceCliTmp = new IPEndPoint(IPAddress.Any, 0);
            Socket tStTmp = (Socket)tResult.AsyncState;
            if (tResult.IsCompleted && (tStTmp != null))
            {
                // 在关闭隧道时会提示错误，但错误对系统并没有影响
                try
                {
                    byte[] tDBufTmp = null;
                    // 接收数据
                    int iRecCount = tStTmp.EndReceiveFrom(tResult, ref tReceCliTmp);
                    try
                    {
                        if (iRecCount > 0)
                        {
                            tDBufTmp = new byte[iRecCount];
                            for (int i = 0; i < iRecCount; i++)
                                tDBufTmp[i] = tReceFromCliBuf[i];
                            RTxBytes?.setRecord(new Record_Byte("Rece(" + tReceCliTmp.ToString() + ")", tDBufTmp));
                        }
                    }
                    finally
                    {
                        // 启动异步接受
                        tReceClientEP = new IPEndPoint(IPAddress.Any, 0);
                        tStTmp.BeginReceiveFrom(tReceFromCliBuf, 0, tReceFromCliBuf.Length,
                            SocketFlags.None, ref tReceClientEP, RecFromCliCallback, tStTmp);
                    }
                    if ((iRecCount > 0) && (tDBufTmp != null))
                        OnRecDatas?.Invoke(this, tReceCliTmp, tDBufTmp);
                }
                catch (Exception ex)
                {
                    ErrorRecorder?.setRecord(ex);
                }
            }
        }

        public override void CloseAll()
        {
            ListenEndPoint = null;
            if (tUDPSocket != null)
                tUDPSocket.Close();
            RunLog?.setRecord(new Record("CloseAll"));
        }

        public void SendRespon(EndPoint tDestEndP, byte[] SendDatas)
        {
            if ((tDestEndP != null) && (SendDatas != null)&&(tUDPSocket!=null))
            {
                // 必须使用原始Socket进行应答，否则下位机无法收到云服务器的应答信息
                tUDPSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
                RTxBytes?.setRecord(new Record_Byte("Resp(" + tDestEndP.ToString() + ")", SendDatas));
                tUDPSocket.SendTo(SendDatas, tDestEndP);
                //RunLog?.setRecord(new Record("Sendto(" + tDestEndP.ToString() + "):" + BaseTools.BytesToStrHex(SendDatas)));
            }
        }
    }

    /// <summary>
    /// UDP客户端，可以提供消息接受功能，使用守护线程监听其他节点或服务器返回的应答数据，但不能承受高速多节点的通讯。
    /// 接口事件：OnRecDatas
    /// 清场函数：CloseAll；必须使用该函数清理守护线程；
    /// 接口函数：SendMsgTo；向指定地址发送数据包，通过接口事件返回收到的数据包。
    /// </summary>
    public class UDPClient: UDPBase
    {

        protected Thread tUDPThdListen { get; set; } = null;

        protected bool iUDPThdRun { get; set; } = false;

        public event UDPRecDataCallBack OnRecDatas;

        public override bool IsOnListen { get { return (tUDPSocket != null) && (tUDPSocket.IsBound); } }

        public UDPClient(IPEndPoint DestIPEP=null) 
        {
            iUDPThdRun = true;
            StarClient(DestIPEP);
        }

        public void StarClient(IPEndPoint DestIPEP=null)
        {
            if (tUDPSocket != null)
                CloseAll();
            ListenEndPoint = DestIPEP;
            if ((ListenEndPoint!=null)&&(tUDPSocket == null))
            {
                tUDPSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
                RunLog?.setRecord(new Record("Bind:" + ListenEndPoint.ToString()));
                tUDPSocket.Bind(ListenEndPoint);//绑定端口号和IP  

                if (tUDPThdListen == null)
                {
                    iUDPThdRun = true;
                    tUDPThdListen = new Thread(ReciveMsg);//开启接收消息线程  
                    tUDPThdListen.Start();
                }
            }
        }

        public override void CloseAll()
        {
            iUDPThdRun = false;
            Thread.Sleep(5);
            try
            {
                if (tUDPThdListen != null)
                {
                    Thread.Sleep(1);
                }
                if (tUDPSocket != null)
                {
                    tUDPSocket.Shutdown(SocketShutdown.Both);
                    tUDPSocket.Close();
                }
            }
            catch (Exception ex)
            {
                if (ErrorRecorder != null)
                    ErrorRecorder.setRecord(ex);
                else
                    throw ex;
            }
            finally
            {
                tUDPSocket = null;
                tUDPThdListen = null;
                iUDPThdRun = false;
                ListenEndPoint = null;
            }

        }

        private void ReciveMsg()
        {
            while (iUDPThdRun)
            {
                try
                {
                    if (tUDPSocket != null)
                    {
                        EndPoint fSourPoint = new IPEndPoint(IPAddress.Any, 0);//用来保存发送方的ip和端口号  
                        byte[] fRxBuf = new byte[1024];
                        int iRecLen = tUDPSocket.ReceiveFrom(fRxBuf, ref fSourPoint);//接收数据报  

                        if (iRecLen > 0)
                        {
                            byte[] fRxDatas = new byte[iRecLen];
                            for (int i = 0; i < iRecLen; i++)
                                fRxDatas[i] = fRxBuf[i];
                            RTxBytes?.setRecord(new Record_Byte("Rece(" + fSourPoint.ToString() + ")", fRxDatas));
                            OnRecDatas?.Invoke(this, fSourPoint, fRxDatas);
                        }
                    }
                }
                catch (Exception ex)
                {
                    ErrorRecorder?.setRecord(ex);
                }
                finally
                {
                    Thread.Sleep(5);
                }
            }
            RunLog?.setRecord(new Record("CloseAll"));
        }

        /// <summary>  
        /// 向特定ip的主机的端口发送数据报  
        /// </summary>  
        public void SendMsgTo(EndPoint fDestEndP, byte[] SendDatas)
        {
            if (IsOnListen && (fDestEndP != null) && (SendDatas != null))
                tUDPSocket.SendTo(SendDatas, fDestEndP);
        }
    }
}
